-- Haskore Live!
--
-- David Siegel <djsiegel@seas.upenn.edu>
-- Jonathan Coveney <jcoveney@wharton.upenn.edu>
--
-- Keys.hs
--

-- | Module 'Keys' provides the programmatic interface for working with the key
-- values defined in KeyConfig.
module Keys ( Key
            , init
            , play, playAsync
            , musicForKey
            , runTests
            ) where

import Test.HUnit
import Control.Monad
import qualified System
import Prelude hiding (init)
import Directory (doesFileExist)
import Control.Concurrent (forkOS, yield)

import Basics
import KeyConfig
import qualified HaskoreUtil as Haskore

-- | Represents a key on the keyboard.
type Key = Char

-- | Music as assigned to each key on the QWERTY keyboard.
musics :: [Music]
musics = [k_1,k_2,k_3,k_4,k_5,k_6,k_7,k_8,k_9,k_0] ++
         [k_q,k_w,k_e,k_r,k_t,k_y,k_u,k_i,k_o,k_p] ++
           [k_a,k_s,k_d,k_f,k_g,k_h,k_j,k_k,k_l]   ++
               [k_z,k_x,k_c,k_v,k_b,k_n,k_m]

-- | List of keys as ordered on a QWERTY keyboard.
keys = "1234567890" ++
       "qwertyuiop" ++
       "asdfghjkl"  ++
        "zxcvbnm"

-- | Map from Keys to Music.
keyMusics :: [(Key, Music)]
keyMusics = zip keys musics

-- | Initialization function for this module. Should be called only once, before
-- this module is used.
init :: IO ()
init = do
    -- putStrLn "Initializing Keys..."
    writeKeyMusics

-- | Given a Key, returns a path to a midi file containing the Music mapped to
-- that key.
file :: Key -> FilePath
file k = let f = if elem k keys then [k] else "a" in
         "midi/" ++ f ++ ".mid"

-- | Given a Key, plays the Music mapped to that key.
play :: Key -> IO ()
play k = do
    let dur = Haskore.musicDuration (musicForKey k)
    let cmd = "pmidi " ++ file k ++ " " ++ show dur
    System.system cmd
    return ()

testPlay = TestCase $ do
    putStrLn "\nTesting Key.play. This test has failed if you do not hear music right now."
    mapM_ play "qwerty"

-- | Asynchronous variant of 'play'.
playAsync :: Key -> IO ()
playAsync k = do
    forkOS $ play k
    yield

-- | Writes midi files for all key-mapped music to disk.
writeKeyMusics :: IO ()
writeKeyMusics = do
    forM_ keyMusics $ \(k, m) ->
        Haskore.musicToMidi (file k) m

testWriteKeyMusics = TestCase $ do
    System.system "rm midi/*.mid"
    writeKeyMusics
    -- Check for every key file.
    forM_ keys $ \k -> do
        exists <- doesFileExist (file k)
        assertBool ("Key file " ++ file k ++ " not found.") exists

-- | Lookup the Music mapped to any given Key. Returns a default value if Key is
-- not mapped.
musicForKey :: Key -> Music
musicForKey k = maybe k_a id (lookup k keyMusics)

testMusicForKey = TestCase $ do
    musicForKey 'a' @?= k_a
    musicForKey '4' @?= k_4
    musicForKey 'm' @?= k_m

-- | Runs unit tests.
runTests = runTestTT $
    TestList [ TestLabel "testWriteKeyMusics" testWriteKeyMusics
             , TestLabel "testPlay" testPlay
             , TestLabel "testMusicForKey" testMusicForKey
             ]
